/* axis.h */

#include <dodo_plot/kernel/plotfwd.h>
#include <dodo_plot/kernel/color.h>
#include <dodo_plot/kernel/position.h>
#include <vector>
#include <string>

#ifndef DODO_AXIS_H
#define DODO_AXIS_H

namespace dodo_plot
{

/**
 * 供绘制坐标轴. 
 * 其中tick_length, minor_tick_length, label_distance等为绝对坐标. 
 *
 * TODO: 与label_align_mode相关的若干功能尚未实现. 
 * 另需增加直接用一组字符串来初始化的功能. 
 */
/// 供绘制坐标轴. 
class Axis
{
public: 
	/// 此枚举值供将次级刻度初始化为对数坐标时使用. 
	enum { MINOR_LOG = -1};
	typedef std::vector<double>::iterator pos_iterator;
	typedef std::vector<double>::const_iterator const_pos_iterator;
public:
	//set
	void set_mode(int mode);
	void set_line_width(double width);
	void set_line_color(const Color& color);
	void set_unit(const std::string& unit);
	void set_tick_length(double length);
	void set_minor_tick_length(double length);
	void set_font_size(double size);
	void set_font_color(const Color& color);
	void set_label_distance(double distance);
	void set_label_align_mode(int mode);
	//get
	/**
	 * 为别的Widget提供的接口. 
	 * 获取刻度值位置序列的开始的常迭代器. 
	 */
	const_pos_iterator tick_begin() const;
	/**
	 * 为别的Widget提供的接口. 
	 * 获取刻度值位置序列的超出末段的常迭代器. 
	 */
	const_pos_iterator tick_end() const;
	/**
	 * 为别的Widget提供的接口. 
	 * 获取刻度值位置所在空间的最小值. 
	 */
	double local_min() const;
	/**
	 * 为别的Widget提供的接口. 
	 * 获取刻度值位置所在空间的最大值. 
	 */
	double local_max() const;
	Color get_line_color() const;
	double get_line_width() const;
public:
	void draw(const Plotter& plotter) const;

public:
	double height() const { return horizontal_ ? 0 : 1.0;}
	double width() const { return horizontal_ ? 1.0 : 0;}
	Position local_pos() const { return Position(0, width(), height(), 0);}
public:
	/**
	 * 坐标轴主要属性由一对double型数字来确定. 
	 * \param min_val 最小值. 
	 * \param max_val 最大值. 
	 * \param tick_interval 主刻度值间隔. 
	 * \param ref_val 任意一个主刻度值. 
	 * \param minor_tick_count 每两个主刻度间的小刻度数, 如为Axis::MINOR_LOG则按对数样式绘制. 
	 * \param mode 样式, 可选为LEFT, RIGHT, TOP和BOTTOM. 
	 * 以BOTTOM为例, 坐标轴为水平, 刻度和标签都在下方.
	 */
	Axis(double min_val, double max_val, 
		double tick_interval, double ref_val,
		int minor_tick_count,
		int mode = BOTTOM);
	
	/**
	 * 坐标轴主要属性由一对double型数字来确定. 
	 * \param min_val 最小值. 
	 * \param max_val 最大值. 
	 * \param tick_interval 主刻度值间隔. 
	 * \param ref_val 任意一个主刻度值. 
	 * \param minor_tick_count 每两个主刻度间的小刻度数, 如为Axis::MINOR_LOG则按对数样式绘制.
	 * \param unit 单位, 将附加在标签中. 
	 * \param mode 样式, 可选为LEFT, RIGHT, TOP和BOTTOM. 
	 * 以BOTTOM为例, 坐标轴为水平, 刻度和标签都在下方.
	 */
	Axis(double min_val, double max_val, 
		double tick_interval, double ref_val,
		int minor_tick_count,
		const std::string& unit,
		int mode = BOTTOM);

	/**
	 * Data_T和Interval_T分别为数据类型和该数据类型的差值的类型.
	 * Interval_T需可以做与double型乘除的算数运算.
	 * 坐标轴主要属性由一对Data_T类型的对象来确定. 
	 * \param min_val 最小值. 
	 * \param max_val 最大值. 
	 * \param tick_interval 主刻度值间隔. 
	 * \param ref_val 任意一个主刻度值. 
	 * \param minor_tick_count 每两个主刻度间的小刻度数, 如为Axis::MINOR_LOG则按对数样式绘制.
	 * \param str_proj 一个将Data_T转换为字符串的函数或仿函数.
	 * \param unit 单位, 将附加在标签中. 
	 * \param mode 样式, 可选为LEFT, RIGHT, TOP和BOTTOM. 
	 * 以BOTTOM为例, 坐标轴为水平, 刻度和标签都在下方.
	 */
	template<class Data_T, class Interval_T, class Data_String_Proj_T>
	Axis(const Data_T& min_val, const Data_T& max_val, 
		const Interval_T& tick_interval, 
		const Data_T& ref_val,
		int minor_tick_count,
		const Data_String_Proj_T& str_proj,
		const std::string& unit,
		int mode = BOTTOM);
	
//	template<class Iter>
//	Axis(Iter b, Iter e, 
//		double first_pos, double last_pos,
//		int minor_tick_count,
//		int mode = BOTTOM);

private:
	template<class Data_T, class Interval_T, class Data_String_Proj_T>
	void fill_data(const Data_T& min_val, const Data_T& max_val,
		const Interval_T& tick_interval, 
		const Data_T& ref_val,
		int minor_tick_count,
		const Data_String_Proj_T& str_proj
		);
public:
	// helper function
	/**
	 * 更改坐标轴样式, 可选值为LEFT, RIGHT, TOP和BOTTOM.
	 * 本接口一般用于别的插件中.
	 */
	void apply_mode(int mode);
	
private:

	double tick_length_;
	double minor_tick_length_;
	double label_distance_;
	
//	double length_;
	double line_width_;
	Color line_color_;

	bool horizontal_;
	double tick_unit_; // 1 or -1
	
	double label_font_size_;
//	Font label_font_;
	Color label_font_color_;

	int label_align_mode_;

	std::string unit_;

	std::vector<double> pos_;
	std::vector<double> values_;
	std::vector<std::string> str_labels_;
	std::vector<double> minor_pos_;
};

//impletement
template<class Data_T, class Interval_T, class Data_String_Proj_T>
Axis::Axis(const Data_T& min_val, const Data_T& max_val, 
	const Interval_T& tick_interval, 
	const Data_T& ref_val,
	int minor_tick_count,
	const Data_String_Proj_T& str_proj,
	const std::string& unit,
	int mode)
	:
	line_width_(0.5),
	line_color_(color_name::BLACK),
	unit_(unit),
	tick_length_(8.0),
	minor_tick_length_(5.0),
	label_font_size_(12),
	label_font_color_(color_name::BLACK),
	label_distance_(15)
{
	apply_mode(mode);
	fill_data(min_val, max_val, 
		tick_interval, ref_val,
		minor_tick_count,
		str_proj
		);
		
}

template<class Data_T, class Interval_T, class Data_String_Proj_T>
void Axis::fill_data(const Data_T& min_val, const Data_T& max_val,
	const Interval_T& tick_interval, 
	const Data_T& ref_val,
	int minor_tick_count,
	const Data_String_Proj_T& str_proj)
{
	double length = (max_val - min_val) / tick_interval;
	double interval_length = 1.0 / length;

	bool has_been_to_the_left_of_min = false;
	double beyond_max_pos = 0;
	
	Data_T ref_val_copy = ref_val;

	while(true)
	{
		double max_ref_distance = (max_val - ref_val_copy) / tick_interval;
		double min_ref_distance = (min_val - ref_val_copy) / tick_interval;
	
		//ref_val_copy is to the left of min_val
		if(min_ref_distance > 0 && max_ref_distance > 0)
		{
			has_been_to_the_left_of_min = true;
			ref_val_copy = ref_val_copy + tick_interval;
		}
		//ref_val_copy is in the middle of min_val and max_val
		else if(min_ref_distance <= 0 && max_ref_distance >= 0)
		{
			if(has_been_to_the_left_of_min)
			{
				pos_.push_back(-min_ref_distance / length);
				str_labels_.push_back(str_proj(ref_val_copy));
				ref_val_copy = ref_val_copy + tick_interval;
			}
			else
			{
				ref_val_copy = ref_val_copy - tick_interval;
			}
		}
		//ref_val_copy is to the right of max_val
		else if(min_ref_distance < 0 && max_ref_distance < 0)
		{
			if(has_been_to_the_left_of_min)
			{
				beyond_max_pos = -min_ref_distance / length;
				break;
			}
			else
			{
				ref_val_copy = ref_val_copy - tick_interval;
			}
		}
	}
	
	std::vector<double> minor_rel_pos;
	if(minor_tick_count == 0)
	{
	}
	else if(minor_tick_count == Axis::MINOR_LOG)
	{
		for(int i = 2; i <= 9; ++i)
		{
			minor_rel_pos.push_back(std::log10(i) * interval_length);
		}
	}
	else
	{
		double minor_interval_length = interval_length / (minor_tick_count + 1);
		for(int i = 1; i <= minor_tick_count; ++i)
		{
			minor_rel_pos.push_back(minor_interval_length * i);
		}
	}

	for(double major_pos = beyond_max_pos - interval_length; major_pos > - interval_length; major_pos -= interval_length)
	{
		for(int i = 0; i < minor_rel_pos.size(); ++i)
		{
			double new_pos = major_pos + minor_rel_pos[i];
			if(new_pos >= 0.0 && new_pos <= 1.0)
			{
				minor_pos_.push_back(new_pos);
			}
		}
	}
}


}

#endif //DODO_AXIS_H

